using Gee;
using Gdk;
using Gtk;

namespace Ribbons {

	/** Window generating synthetic events to window-less widgets. */
	public class SyntheticWindow : Gtk.Window {

		private Gee.List<Widget> _last_hovered_widgets;

		construct {
			_last_hovered_widgets = new Gee.ArrayList<Widget> ();
		}

		public SyntheticWindow (Gtk.WindowType type) {
			this.type = type;
		}

		protected override bool event (Gdk.Event evnt) {
			// This method is hooked to block the event as soon as possible if required

			if (evnt.any.window == this.window) {
				switch (evnt.type) {
				case EventType.BUTTON_PRESS:
				case EventType.BUTTON_RELEASE:
				case EventType.3BUTTON_PRESS:
				case EventType.2BUTTON_PRESS:
					var eb = evnt.button;
					return propagate_event_given_coordinate (evnt, eb.x, eb.x_root, eb.y, eb.y_root);

				case EventType.MOTION_NOTIFY:
					var em = evnt.motion;
					return propagate_event_given_coordinate (evnt, em.x, em.x_root, em.y, em.y_root);

				case EventType.LEAVE_NOTIFY:
					foreach (var widget in _last_hovered_widgets) {
						widget.event (evnt);
					}
					_last_hovered_widgets.clear ();
//					return base.event (evnt);	// XXX-GEETK
					return false;
				}
			}
//			return base.event (evnt);	// XXX-GEETK
			return false;
		}

		private bool propagate_event_given_coordinate (Gdk.Event evnt, double X, double x_root, double Y, double y_root) {
			int x = (int) X;
			int y = (int) Y;
			Container current = this;	// Current container containing the coordinate
			Widget match = this;		// Current match for the position
			int matched_pos = 0;		// Current position in last_hovered_widgets

			while (matched_pos < _last_hovered_widgets.size) {
				var candidate = _last_hovered_widgets[matched_pos];
				if (candidate.parent == (Widget) current) {	// Is it still a child of the current container ?
					Gdk.Rectangle alloc = (Gdk.Rectangle) candidate.allocation;
					if (!Gdk.Rect.contains (alloc, x, y)) {	// Does it contain the coordinate ?
						break;
					}
				}
				current = candidate as Container;
				match = candidate;
				++matched_pos;
			}

			if (matched_pos < _last_hovered_widgets.size) {	// Not all widgets match
				// Send a leave notify
				send_synthetic_event (EventType.LEAVE_NOTIFY, evnt, X, x_root, Y, y_root,
										_last_hovered_widgets, matched_pos);
				
				// Remove them
				list_remove_range (_last_hovered_widgets, matched_pos,
							_last_hovered_widgets.size - matched_pos);
			}

			while (current != null) {
				Container next = null;
				foreach (var child in current.get_children ()) {
					if ((child.get_flags () & WidgetFlags.NO_WINDOW) != 0) {
						Gdk.Rectangle alloc = (Gdk.Rectangle) child.allocation;
						if (Gdk.Rect.contains (alloc, x, y)) {
							_last_hovered_widgets.add (child);
							match = child;
							next = child as Container;
							break;
						}
					}
				}
				current = next;
			}

			if (matched_pos < _last_hovered_widgets.size) {	// New widgets have been found
				// Send an enter notify
				send_synthetic_event (EventType.ENTER_NOTIFY, evnt, X, x_root, Y, y_root,
										_last_hovered_widgets, matched_pos);
			}

			if (match == this) {	// No widget found, the window keeps the event
//				return base.event (evnt);	// XXX-GEETK
				return false;
			} else {	// A widget has been found, let's send it the event
				match.event (evnt);
				return true;
			}
		}

		private void send_synthetic_event (EventType type, Event original_event,
		                                   double x, double x_root,
		                                   double y, double y_root,
		                                   Gee.List<Widget> widgets, int index)
		{
			var se = EventCrossing () {
				detail = NotifyType.ANCESTOR,
				focus = false,
				mode = CrossingMode.NORMAL,
				send_event = 0,
				state = 0,
				subwindow = null,
//				time = DateTime.Now.Ticks / 10000,	// TODO: the real value shoud be the uptime I think
				time = 0,
				type = type,
				window = original_event.any.window,
				x = x,
				x_root = x_root,
				y = y,
				y_root = y_root
			};

			Event managed_event = original_event.copy ();
			managed_event.type = EventType.MOTION_NOTIFY;
			managed_event.crossing = se;

			for (int i = index; i < widgets.size; i++) {
				widgets[i].event (managed_event);
			}
		}
	}
}

